package ssh;

import java.io.IOException;
import java.io.InputStream;
import org.apache.log4j.Logger;
import model.IPAddress;
import utils.IllegalValueException;
import ch.ethz.ssh2.ChannelCondition;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;

public class SSHConnectionHandler {
	
	private static final Logger log = Logger.getLogger(SSHConnectionHandler.class);

	private byte[] stdOut = new byte[8192];
	private byte[] stdErr = new byte[8192];
	private Connection connection;
	private Session session;
	private int lenStdOut;
	private int lenStdErr;
	
	/* Timeout in millisecond */
	private static int CONNECTION_TIMEOUT=1000; 

	/**
	 * Crea un gestore per una singola connessione ssh2
	 * 
	 * @param conn
	 */
	public SSHConnectionHandler(Connection conn) {
		connection = conn;
		session = null;
		lenStdErr = 0;
		lenStdOut = 0;
	}

	/**
	 * Invia un comando sulla connessione ssh2. Il risultato del comando, in
	 * termini di risposta della connessione ssh, viene riportato in stdOut ed in
	 * stdErr.
	 * 
	 * Qualora il comando eseguito non dovesse terminare con valore di ritorno
	 * 0, viene lanciata una eccezione.
	 * 
	 * @param command
	 * @throws SSHCommandException
	 * @throws InterruptedException 
	 */
	public int sendCommand(String command) throws SSHCommandException{
		
		log.debug("Executing command: " + command);
		try {
			
			if (this.session != null)
				this.session.close();
			session = this.connection.openSession();
			session.execCommand(command);
			
			InputStream stdout = session.getStdout();
			InputStream stderr = session.getStderr();

			while (true)
			{	
				
				if ((stdout.available() == 0) && (stderr.available() == 0))
				{
					int conditions = session.waitForCondition(ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA
									| ChannelCondition.EOF, 2000);

					if ((conditions & ChannelCondition.TIMEOUT) != 0)
					{	throw new IOException("Timeout while waiting for data from peer."); }
					
					if ((conditions & ChannelCondition.EOF) != 0)
					{	if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) == 0) 
						{break;	}
					}

					if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) == 0)
					{	throw new IllegalStateException("Unexpected condition result (" + conditions + ")");}
				}

				while (stdout.available() > 0)
				{	lenStdOut = stdout.read(stdOut);	}

				while (stderr.available() > 0)
				{	lenStdErr = stderr.read(this.stdErr);		}
			}
			
			try{
			
				Thread.sleep(CONNECTION_TIMEOUT);
				
				return session.getExitStatus().intValue();
				
			}catch (Exception e){
				log.error("ERROR: executing comand "+ command );
				e.printStackTrace();
				return -1;
			}

		} catch (IOException e) {
			throw new SSHCommandException(e.getMessage());
		} 
		
	}
	
	/**
	 * Invia un comando sulla connessione ssh2. Il risultasto del comando, in
	 * termini di risposta della connessione ssh, viene riportato nello stdOut,
	 * che puo' essere letto dopo aver eseguito il comando.
	 * 
	 * Il valore di ritorno con cui termina il comando viene restituito dal
	 * metodo. Questa metodo puo' essere usato per eseguire quei comandi la cui
	 * mancata terminazione con 0 non e' considerata un'eccezione.
	 * 
	 * @param command
	 * @throws IOException
	 * @throws SSHCommandException
	 */
	public void sendCommandNoErrorControl(String command) throws SSHCommandException {
		
		log.debug("Executing command: " + command);
		try {
			
			if (this.session != null)
				this.session.close();
			session = this.connection.openSession();
			session.execCommand(command);
			
			InputStream stdout = session.getStdout();
			InputStream stderr = session.getStderr();

			while (true)
			{
				if ((stdout.available() == 0) && (stderr.available() == 0))
				{
					int conditions = session.waitForCondition(ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA
									| ChannelCondition.EOF, 2000);

					if ((conditions & ChannelCondition.TIMEOUT) != 0)
					{	throw new IOException("Timeout while waiting for data from peer."); }
					
					if ((conditions & ChannelCondition.EOF) != 0)
					{	if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) == 0) 
						{break;	}
					}

					if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) == 0)
					{	throw new IllegalStateException("Unexpected condition result (" + conditions + ")");}
				}

				while (stdout.available() > 0)
				{	lenStdOut = stdout.read(stdOut);	}

				while (stderr.available() > 0)
				{	lenStdErr = stderr.read(this.stdErr);		}
				
			}

		} catch (IOException e) {
			throw new SSHCommandException(e.getMessage());
		}
	}	

	/**
	 * Chiude la connessione ssh2 legata a questo gestore
	 */
	public void dispose() {
		if (this.session != null)
			this.session.close();

		this.connection.close();

		log.debug("Disconnected");
	}
	
	/**
	 * @return the stdOut
	 */
	public String getStdOutString() {
		return new String(stdOut,0,lenStdOut-1);
	}
	
	/**
	 * @return the stdOut
	 */
	public String getStdErrString() {
		return new String(stdErr,0,lenStdErr-1);
	}
	
	

	public static SSHConnectionHandler createSSHConnectionHandler(
			IPAddress ipAddress, int port, String username, String password)
			throws IOException {

		log.debug("Try to connect to " + ipAddress.getAddress() + ":" + port + " ...");
		
		Connection conn = new Connection(ipAddress.getAddress());
		conn.connect(null, CONNECTION_TIMEOUT, CONNECTION_TIMEOUT);
		/* Autenticazione */
		boolean isAuthenticated = conn.authenticateWithPassword(username,password);
		if (isAuthenticated == false)
			throw new IOException("Authentication failed.");
		
		log.debug("... connected to " + ipAddress + ":" + port);
		return new SSHConnectionHandler(conn);
	}
	
	public static void main(String[] args) throws IOException, IllegalValueException, InterruptedException
	{	
		try {
			IPAddress ipAddres = new IPAddress(4,"192.168.74.17","255.255.255.0");
			SSHConnectionHandler handler1 = SSHConnectionHandler.createSSHConnectionHandler(ipAddres, 22, "root", "nepadmpwd");
			
			handler1.sendCommand("ebtables -t filter -L FORWARD");
			System.out.print(handler1.getStdOutString());
			
			handler1.dispose();
			
		} catch (SSHCommandException e) {
			e.printStackTrace();
		}		
	}

}